package com.platform.gateway.filter;

import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.platform.common.entity.UserSessionDTO;
import com.platform.common.enums.RoleEnum;
import com.platform.common.util.JwtUtils;
import com.platform.common.util.NonExceptionOptional;
import com.platform.gateway.client.PermissionClient;
import com.platform.gateway.config.FilterProperties;
import com.platform.gateway.util.FilterUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Locale;
import java.util.Optional;


/**
 * 鉴权, 登录, 登出 处理
 * @author zhangt2333
 **/

@Slf4j
@Component
@EnableConfigurationProperties({FilterProperties.class})
public class LoginFilter implements GlobalFilter, Ordered {

    /**
     * Make sure that order is bigger than NettyWriteResponseFilter‘s
     */
    @Override
    public int getOrder() {
        return 0;
    }

    @Autowired
    private FilterProperties filterProp;

    @Autowired
    private PermissionClient permissionClient;

    @Autowired
    private JwtUtils jwtUtils;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String requestUrl = request.getPath().toString();
//        String realIp = FilterUtils.getRealIp(request);
//        log.info("Filter From: {}\tUrl: {}\tParams: {}", realIp, requestUrl, request.getQueryParams());
        // 取 token
        String token = Optional.of(request)
                .map(HttpMessage::getHeaders)
                .map(o -> o.get(UserSessionDTO.HEADER_KEY))
                .map(o -> o.get(0))
                .orElse(null);
        // 无(有效)token，非 allowUrl
        boolean isAllowPath = isAllowPath(requestUrl);
        if (!jwtUtils.checkToken(token) && !isAllowPath) {
            return FilterUtils.returnWithStatus(exchange, HttpStatus.UNAUTHORIZED, " 你的账号没有该权限或未登录! ");
        }

        UserSessionDTO userSessionDTO = JwtUtils.getUserByToken(token);

        // 鉴权
        if (userSessionDTO != null) {
            List<String> urlRoles = NonExceptionOptional.ofNullable(() -> permissionClient.urlToRoles(requestUrl))
                    .orElse(Lists.newArrayList());
            String role = NonExceptionOptional.ofNullable(userSessionDTO::getRole)
                    .orElse("");

            // 不为管理员，不为无权限限制的URL，不为白名单URL，且角色不符合URL角色集合
            if (!RoleEnum.ADMIN.getName().equals(role) && !urlRoles.contains(RoleEnum.ALL.getName()) && !urlRoles.contains(role) && !isAllowPath) {
                log.warn("无权限 {} {}", userSessionDTO, requestUrl);
                return FilterUtils.returnWithStatus(exchange, HttpStatus.UNAUTHORIZED, String.format("该用户在此url上无权限 '%s'", requestUrl));
            }

            // 装饰器 修改 getHeaders 方法
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public HttpHeaders getHeaders() {
                    MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap(8, Locale.ENGLISH));
                    multiValueMap.putAll(super.getHeaders());
    //              multiValueMap.remove("cookie"); // 在此处已解码 token, 故不下传省流量, 如果后续有多值 cookie 此处需要修改
                    multiValueMap.remove(UserSessionDTO.HEADER_KEY);
                    multiValueMap.add(UserSessionDTO.HEADER_KEY, JSONUtil.toJsonStr(userSessionDTO));
                    multiValueMap.remove(UserSessionDTO.KEY_USERID_PREFIX);
                    multiValueMap.add(UserSessionDTO.KEY_USERID_PREFIX, userSessionDTO.getId().toString());
//                    for (Field field : UserSessionDTO.class.getDeclaredFields()) { // 删掉对 UserSession 逐个字段加入 header 的操作
//                        try {
//                            field.setAccessible(true);
//                            multiValueMap.remove("authorization-" + field.getName());
//                            multiValueMap.add("authorization-" + field.getName(), String.valueOf(field.get(userSessionDTO)));
//                        } catch (IllegalAccessException e) {
//                            log.error("getHeaders Decorator", e);
//                        }
//                    }
                    return new HttpHeaders(multiValueMap);
                }
            };
            return chain.filter(exchange.mutate().request(decorator).build()).then(thenHandleSession(exchange));
        }
        return chain.filter(exchange).then(thenHandleSession(exchange));
    }

    private Mono<Void> thenHandleSession(ServerWebExchange exchange) {
        String userId = Optional.of(exchange)
                .map(ServerWebExchange::getRequest)
                .map(HttpMessage::getHeaders)
                .map(o -> o.get(UserSessionDTO.KEY_USERID_PREFIX))
                .map(o -> o.get(0))
                .orElse(null);
        // 返回响应时检查是否注销
        return Mono.fromRunnable(() -> {
            Optional.ofNullable(exchange.getResponse().getHeaders().get(UserSessionDTO.HEADER_KEY))
                    .map(o -> o.get(0))
                    .filter(UserSessionDTO.HEADER_VALUE_LOGOUT::equals)
                    .ifPresent(o -> jwtUtils.removeToken(userId));
        });
    }

    private boolean isAllowPath(String requestUrl) {
        for (String allowPath : filterProp.getAllowPaths()) {
            if (requestUrl.startsWith(allowPath)) {
                return true;
            }
        }
        return false;
    }

}
